package com.alpha.processor.processor.tools;

import com.alpha.binder.anno.app.BinderApp;
import com.alpha.binder.anno.processor.DataProcessor;
import com.alpha.binder.anno.interfaces.ProcessorInterface;
import com.alpha.binder.anno.provider.Provider;
import com.alpha.binder.anno.connector.CreateConnector;
import com.alpha.binder.anno.motion.ClickAspect;
import com.alpha.binder.anno.motion.OnViewClick;
import com.alpha.binder.anno.view.View;
import com.alpha.binder.anno.view.ViewBinder;
import com.alpha.binder.anno.interfaces.ViewInterface;
import com.alpha.processor.creators.binder_file.BinderCreateModel;
import com.alpha.processor.creators.binder_file.BinderFileCreator;
import com.alpha.processor.creators.connector_file.ConnectorFileCreator;
import com.alpha.processor.creators.interface_file.ProviderInterfaceFileCreator;
import com.alpha.processor.processor.anno_processor.AppAnnoProcessor;
import com.alpha.processor.processor.anno_processor.ClickAspectAnnoProcessor;
import com.alpha.processor.processor.anno_processor.InterfaceAnnoProcessor;
import com.alpha.processor.processor.anno_processor.ProviderAnnoProcessor;
import com.alpha.processor.processor.anno_processor.base.FileCreateAnnoProcessor;
import com.google.auto.service.AutoService;
import com.squareup.javapoet.JavaFile;

import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;

import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Messager;
import javax.annotation.processing.ProcessingEnvironment;
import javax.annotation.processing.RoundEnvironment;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.lang.model.util.Elements;
import javax.tools.Diagnostic;

@AutoService(javax.annotation.processing.Processor.class)
public class CustomProcessor extends AbstractProcessor{

    private Messager messager;
    private Elements elementUtils;
    private ProcessorTool logger;

    private AppAnnoProcessor binderAnnoProcessor;

    private InterfaceAnnoProcessor interfaceAnnoProcessor;
    
    private FileCreateAnnoProcessor connectorCreateAnnoProcessor;

    private ClickAspectAnnoProcessor clickAspectAnnoProcessor;

    private ProviderAnnoProcessor providerAnnoProcessor;

    @Override
    public synchronized void init(ProcessingEnvironment processingEnvironment) {
        super.init(processingEnvironment);

        messager = processingEnv.getMessager();
        elementUtils = processingEnv.getElementUtils();
        logger = new ProcessorTool(processingEnv);
    }

    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment){

        try {
            messager.printMessage(Diagnostic.Kind.NOTE, "processing...");

            logger.addArgs("");
            logger.addArgs(" ------------ BinderApp ------------ ");
            logger.addArgs("");
            if (binderAnnoProcessor == null){
                binderAnnoProcessor = new AppAnnoProcessor(messager,elementUtils,logger);
            }
            binderAnnoProcessor.processAnno(roundEnvironment);

            logger.addArgs("");
            logger.addArgs(" ------------ ClickAspect ------------ ");
            logger.addArgs("");
            if (clickAspectAnnoProcessor == null){
                clickAspectAnnoProcessor = new ClickAspectAnnoProcessor(messager,elementUtils,logger);
            }
            clickAspectAnnoProcessor.processAnno(roundEnvironment);

            logger.addArgs("");
            logger.addArgs(" ------------ ViewInterface && ProcessorInterface ------------ ");
            logger.addArgs("");
            if (interfaceAnnoProcessor == null){
                interfaceAnnoProcessor = new InterfaceAnnoProcessor(messager,elementUtils,logger);
            }
            interfaceAnnoProcessor.processAnno(roundEnvironment);

            logger.addArgs("");
            logger.addArgs(" ------------ Provider ------------ ");
            logger.addArgs("");
            if (providerAnnoProcessor == null){
                providerAnnoProcessor = new ProviderAnnoProcessor(messager,elementUtils,logger,interfaceAnnoProcessor);
            }
            providerAnnoProcessor.processAnno(roundEnvironment);

            logger.addArgs("");
            logger.addArgs(" ------------ ViewBinder ------------ ");
            logger.addArgs("");
            if (connectorCreateAnnoProcessor == null){
                connectorCreateAnnoProcessor = new FileCreateAnnoProcessor(messager,elementUtils,logger);
            }
            connectorCreateAnnoProcessor.processViewBinderAnno(roundEnvironment);

            logger.addArgs("");
            logger.addArgs(" ------------ DataProcessor ------------ ");
            logger.addArgs("");
            if (connectorCreateAnnoProcessor == null){
                connectorCreateAnnoProcessor = new FileCreateAnnoProcessor(messager,elementUtils,logger);
            }
            connectorCreateAnnoProcessor.processDataProcessorAnno(roundEnvironment);

            logger.addArgs("");
            logger.addArgs(" ------------ OnViewClick ------------ ");
            logger.addArgs("");
            if (connectorCreateAnnoProcessor == null){
                connectorCreateAnnoProcessor = new FileCreateAnnoProcessor(messager,elementUtils,logger);
            }
            connectorCreateAnnoProcessor.processOnViewClickAnno(roundEnvironment);

            logger.addArgs("");
            logger.addArgs(" ------------ CreateConnector ------------ ");
            logger.addArgs("");
            if (connectorCreateAnnoProcessor == null){
                connectorCreateAnnoProcessor = new FileCreateAnnoProcessor(messager,elementUtils,logger);
            }
            connectorCreateAnnoProcessor.processCreateConnectorAnno(roundEnvironment);

            messager.printMessage(Diagnostic.Kind.NOTE, "process finish ...");
            logger.addArgs("");
            logger.addArgs(" ------------ Process Finish ------------ ");
            logger.addArgs("");
            messager.printMessage(Diagnostic.Kind.NOTE, "sync elements ...");

            //Sync Some Necessary Elements In Creators
            syncElementDatas();

            //  sync superclass datas && remove superclass creator
            checkCreatorClass();

            // Generate Files
            generateJavaFiles();

            // Print Log
            logger.printLog();

        } catch (Exception e) {
            e.printStackTrace();
        }

        return true;
    }

    private void syncElementDatas(){
        connectorCreateAnnoProcessor.syncNecessaryDatas(messager,clickAspectAnnoProcessor.getClickAspectElement(),
                providerAnnoProcessor.getInterfaceCreateMap(),interfaceAnnoProcessor.getInterfaceInfoModel());

        BinderCreateModel binderCreateModel = binderAnnoProcessor.getBinderFileCreator().getCreateModel();
        binderCreateModel.syncGenerateClassMap(connectorCreateAnnoProcessor.getConnectorCreatorMap());
        binderCreateModel.updateConnectorInterfaceInfoModel(interfaceAnnoProcessor.getInterfaceInfoModel());
    }

    // Check Every Marked Class ---  Class Can Not Be Abstract
    private void checkCreatorClass(){
        messager.printMessage(Diagnostic.Kind.NOTE,"check classes");

        Map<String,ConnectorFileCreator> connectorCreatorMap = connectorCreateAnnoProcessor.getConnectorCreatorMap();
        for (String key : connectorCreatorMap.keySet()){
            ConnectorFileCreator targetCreator = connectorCreatorMap.get(key);
            TypeElement typeElement = (TypeElement) targetCreator.getClassElement();
            Set<Modifier> modifierSet = typeElement.getModifiers();
            if (modifierSet.contains(Modifier.ABSTRACT)){
                throw new AnnoProcessorToolException("Class Can Not Be Abstract --- " + typeElement.getQualifiedName());
            }
        }
    }

    private void generateJavaFiles() throws Exception{
        logger.addArgs("");
        logger.addArgs("");
        logger.addArgs(" ------------ Create Files ------------ ");
        logger.addArgs("");
        logger.addArgs("");
        String currentGenarateFileFullName;

        logger.addArgs("");
        logger.addArgs(" ------------ Create Connector Files ------------ ");
        logger.addArgs("");
        messager.printMessage(Diagnostic.Kind.NOTE, "create connector files");
        // Generate Connector Files
        for (String key : connectorCreateAnnoProcessor.getConnectorCreatorMap().keySet()) {
            ConnectorFileCreator fileCreator = connectorCreateAnnoProcessor.getConnectorCreatorMap().get(key);
            currentGenarateFileFullName = fileCreator.getGenerateFileFullName();
            messager.printMessage(Diagnostic.Kind.NOTE, "create: " + currentGenarateFileFullName);
            logger.addArgs(currentGenarateFileFullName);
            JavaFile javaFile = JavaFile.builder(fileCreator.getFilePackageName(), fileCreator.genarateTargetFile()).build();
            // Create File
            javaFile.writeTo(processingEnv.getFiler());
        }

        logger.addArgs("");
        logger.addArgs(" ------------ Create Provider Files ------------ ");
        logger.addArgs("");
        messager.printMessage(Diagnostic.Kind.NOTE, "create provider files");
        for (String key : providerAnnoProcessor.getInterfaceCreateMap().keySet()) {
            ProviderInterfaceFileCreator fileCreator = providerAnnoProcessor.getInterfaceCreateMap().get(key);
            currentGenarateFileFullName = fileCreator.getGenerateFileFullName();
            messager.printMessage(Diagnostic.Kind.NOTE, " --> create " + currentGenarateFileFullName);
            JavaFile javaFile = JavaFile.builder(fileCreator.getFilePackageName(), fileCreator.genarateTargetFile()).build();
            // Create File
            javaFile.writeTo(processingEnv.getFiler());
        }

        logger.addArgs("");
        logger.addArgs(" ------------ Create Binder Files ------------ ");
        logger.addArgs("");
        messager.printMessage(Diagnostic.Kind.NOTE, "create binder files");
        BinderFileCreator binderFileCreator = binderAnnoProcessor.getBinderFileCreator();
        currentGenarateFileFullName = binderFileCreator.getGenerateFileFullName();
        messager.printMessage(Diagnostic.Kind.NOTE, "create: " + currentGenarateFileFullName);
        logger.addArgs(currentGenarateFileFullName);
        JavaFile javaFile = JavaFile.builder(binderFileCreator.getFilePackageName(), binderFileCreator.genarateTargetFile()).build();
        // Create File
        javaFile.writeTo(processingEnv.getFiler());
    }

    @Override
    public SourceVersion getSupportedSourceVersion() {
        return SourceVersion.latestSupported();
    }

    @Override
    public Set<String> getSupportedAnnotationTypes() {
        HashSet<String> supportTypes = new LinkedHashSet<>();

        supportTypes.add(BinderApp.class.getCanonicalName());

        supportTypes.add(ClickAspect.class.getCanonicalName());

        supportTypes.add(CreateConnector.class.getCanonicalName());
        supportTypes.add(ProcessorInterface.class.getCanonicalName());
        supportTypes.add(ViewInterface.class.getCanonicalName());

        supportTypes.add(DataProcessor.class.getCanonicalName());

        supportTypes.add(ViewBinder.class.getCanonicalName());
        supportTypes.add(View.class.getCanonicalName());
        supportTypes.add(OnViewClick.class.getCanonicalName());

        supportTypes.add(CreateConnector.class.getCanonicalName());

        supportTypes.add(Provider.class.getCanonicalName());

        return supportTypes;
    }
}